package com.xiongzai.lambda;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;


/**
 * Stream 流常用方法汇总
 * @author 熊崽
 */
public class StreamApi {
    private static final Logger logger = LoggerFactory.getLogger(Stream.class);
    
    public static void main(String[] args) {
        /*Java 8 在推出流的同时，对集合框架也进行了一些比较大变更。主要是在 Collection 接口上提供了两种生成 Stream 的方法:
    
        stream() 方法，该方法以集合作为源，返回集合中的所有元素以在集合中出现的顺序组成的流。简单的说就是->串行
        parallelStream() 方法，该方法以集合作为源，返回一个支持并发操作的流。            简单的说就是->并行
        注意：如果数据量超过10w，推荐使用并行流 parallelStream()
        并行流  parallelStream() 里面不要做添加或删除操作，例如 list集合的 add() remove()
        如果需要修改数据，可以考虑使用peek()代替
        remove() 可以考虑用 skip() 代替*/
        List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5, 1, 8, 9);
        logger.info(new StringBuilder().append("List->").append(numbers).toString());
        
        /*list对象*/
        List<Student> students = Arrays.asList(new Student("张三", "男", 18), new Student("李四", "男", 20)
                , new Student("王五", "男", 35), new Student("小红", "女", 16)
                , new Student("小明", "女", 23), new Student("小白", "男", 25)
                , new Student("小黑", "男", 25), new Student("小萌", "女", 18)
                , new Student("小黑", "男", 25), new Student("小萌", "女", 18));
        
        forEach(numbers, students);
        
        map(numbers, students);
        
        distinct(numbers, students);
        
        filter(numbers, students);
        
        limit(numbers, students);
        
        sorted(numbers, students);
        
        Statistics(numbers, students);
        
        peek(numbers, students);
        
        skip(numbers, students);
        
        toArray(numbers, students);
        
        reduce(numbers, students);
        
        Collectors(students);
        
        find(numbers);
        
    }
    
    /**
     * 获取流中的第一个Optional<T>元素，如果流为空，则返回一个空的Optional
     * findFirst: 返回描述此流的第一个元素的可选参数，如果流为空，则返回空的可选参数。如果流没有相遇顺序，则可以返回任何元素。这是一次短路端子操作。
     * findAny: 返回描述此流的第一个元素的可选参数，如果流为空，则返回空的可选参数。如果流没有相遇顺序，则可以返回任何元素。这是一次短路端子操作。
     * 此操作的行为是明确不确定的；它可以自由选择流中的任何元素。这是为了在并行操作中实现最高性能；代价是对同一个源的多个调用可能不会返回相同的结果。
     * (如果需要稳定的结果，请改用findFirst()。)
     */
    private static void find(List<Integer> numbers) {
        Optional<Integer> first = numbers.stream().findFirst();
        logger.info(new StringBuilder().append("findFirst()->").append(first.get()).toString());
        
        Optional<Integer> any = numbers.stream().findAny();
        logger.info(new StringBuilder().append("findAny()->").append(any.get()).toString());
    }
    
    /*除了常用的 toList\toMap，还有分组groupingBy，拼接joining等等*/
    private static void Collectors(List<Student> students) {
        Map<String, List<Student>> map = students.stream().collect(Collectors.groupingBy(Student::getSex));
        logger.info(new StringBuilder().append("Collectors()男->").append(map.get("男").stream().map(Student::getName).collect(Collectors.toList())).toString());
        logger.info(new StringBuilder().append("Collectors()女->").append(map.get("女").stream().map(Student::getName).collect(Collectors.toList())).toString());
    }
    
    /*reduction 操作（也称为fold），通过反复的对一个输入序列的元素进行某种组合操作
    （如对数的集合求和、求最大值，或者将所有元素放入一个列表），
    最终将其组合为一个单一的概要信息。stream类包含多种形式的通用reduction操作，
    如reduce和collect，以及其他多种专用reduction形式：sum，max或者count。*/
    private static void reduce(List<Integer> numbers, List<Student> students) {
        /*JDK7的写法*/
        int sum = 0;
        for (int x : numbers) {
            sum += x;
        }
        logger.info(new StringBuilder().append("for()->").append(sum).toString());
        
        Integer reduce = numbers.stream().reduce(0, (x, y) -> x + y);
        logger.info(new StringBuilder().append("reduce()->").append(reduce).toString());
        /*这些reduction操作可以安全的并发执行而几乎不需要任何修改。*/
        Integer reduce1 = numbers.parallelStream().reduce(0, Integer::sum);
        logger.info(new StringBuilder().append("reduce()->").append(reduce1).toString());
        
        
        /*下面有一个复杂的示例*/
        ArrayList<Integer> accResult = Stream.of(1, 2, 3, 4)
                //第一个参数，初始值为ArrayList
                .reduce(new ArrayList<Integer>(),
                        //第二个参数，实现了BiFunction函数式接口中apply方法，并且打印BiFunction
                        new BiFunction<ArrayList<Integer>, Integer, ArrayList<Integer>>() {
                            @Override
                            public ArrayList<Integer> apply(ArrayList<Integer> acc, Integer item) {
                                
                                acc.add(item);
                                System.out.println("item: " + item);
                                System.out.println("acc+ : " + acc);
                                System.out.println("BiFunction");
                                return acc;
                            }
                            //第三个参数---参数的数据类型必须为返回数据类型，改参数主要用于合并多个线程的result值
                            // （Stream是支持并发操作的，为了避免竞争，对于reduce线程都会有独立的result）
                        }, new BinaryOperator<ArrayList<Integer>>() {
                            @Override
                            public ArrayList<Integer> apply(ArrayList<Integer> acc, ArrayList<Integer> item) {
                                System.out.println("BinaryOperator");
                                acc.addAll(item);
                                System.out.println("item: " + item);
                                System.out.println("acc+ : " + acc);
                                System.out.println("--------");
                                return acc;
                            }
                        });
        
        ArrayList<Integer> newList = new ArrayList<>();
        ArrayList<Integer> accResult2 = Stream.of(1, 2, 3, 4)
                .reduce(newList,
                        /* acc 就是 newList */
                        (acc, item) -> {
                            acc.add(item);
                            System.out.println("item: " + item);
                            System.out.println("acc+ : " + acc);
                            System.out.println("BiFunction");
                            return acc;
                        }, (acc, item) -> null);
        logger.info(new StringBuilder().append("reduce()->").append(accResult2).toString());
    }
    
    /**
     * 使用提供的generator函数返回包含此流的元素的数组，以分配返回的数组，以及分区执行或调整大小可能需要的任何其他数组。
     * 简单说：就是新建一个包含此流中元素的数组
     */
    private static void toArray(List<Integer> numbers, List<Student> students) {
        Integer[] arrayInteger = numbers.stream()
                .filter(e -> multip(e) > 18)
                .toArray(Integer[]::new);
        logger.info(new StringBuilder().append("toArray()->").append(arrayInteger).toString());
        
        Student[] array = students.stream()
                .filter(e -> e.getAge() > 18)
                .toArray(Student[]::new);
        logger.info(new StringBuilder().append("toArray()->").append(array).toString());
    }
    
    /** 在丢弃流的第一个n元素后，返回由该流的n元素组成的流。如果此流包含少于n元素，那么将返回一个空流。 */
    private static void skip(List<Integer> numbers, List<Student> students) {
        /*筛选性别为女，且不重复的学生名称，丢弃第一个，并排序,拼接姓名并且以逗号分隔*/
        String list = students.stream()
                .filter(student -> student.getSex().equals("女"))
                .peek(student -> student.setAge(student.getAge() + 1))
                .map(Student::getName)
                .distinct()
                .skip(1)
                /*.limit(2)*/
                .sorted().collect(Collectors.joining(","));
        logger.info(new StringBuilder().append("skip()->").append(list).toString());
    
        /*skip 一般是配合 limit 使用，用于分页，在数据不超过百万级别是够用的。
        其实java都不适合处理超过500w的数据量，18年测试过，直接导致OOM(内存爆了) */
        List<?> result = getPageResult(numbers, 1, 5);
        logger.info(new StringBuilder().append("skip(),limit():第一页->").append(result).toString());
        
    }
    
    /**
     * 分页方法
     *
     * @param list 要分页的数据
     * @param page 当前页
     * @param size 每页条数
     */
    public static List<?> getPageResult(List<?> list, int page, int size) {
        //模拟分页效果
        return list.stream()
                .skip((page - 1) * size)
                .limit(size)
                .collect(Collectors.toList());
        /*注意：
        简单的数据类型的分页或者量小的数据可以使用，但是复杂的数据类型分页不推荐这种，随着数据的增多，效率会变得比较慢。*/
    }
    
    /**
     * peek() 返回由该流的元素组成的流，另外在从生成的流中消耗元素时对每个元素执行提供的操作。
     * 对于并行流管线，可以在上游操作的任何时间和任何线程中调用该元素可用的动作。 如果操作修改共享状态，则负责提供所需的同步。
     * 简单来说：map是将一个list转化为另一个list，peek就是直接对原list操作
     * 实战中用法：
     * map()   如果我们要从 Collection<T> 中获取 T 的某个属性的集合时用 map
     * peek()  对 Collection<T> 中的 T 的某些属性进行批处理的时候用 peek
     */
    private static void peek(List<Integer> numbers, List<Student> students) {
        List<Integer> demo = numbers.stream()
                .filter(e -> e > 3)
                .peek(e -> System.out.println("filter() ->: " + e))
                .map(StreamApi::multip)
                .peek(e -> System.out.println("map() ->: " + e))
                .collect(Collectors.toList());
        logger.info(new StringBuilder().append("peek()->").append(demo).toString());
        
        /*筛选性别为女，且不重复的学生名称，只取前2，并排序,拼接姓名并且以逗号分隔,年后，她们又大了一岁*/
        String list = students.stream()
                .filter(student -> student.getSex().equals("女"))
                .peek(student -> student.setAge(student.getAge() + 1))
                .map(Student::getName)
                .distinct()
                .limit(2)
                .sorted().collect(Collectors.joining(","));
        logger.info(new StringBuilder().append("peek()->").append(list).toString());
    }
    
    /*Statistics() Java 8 同时新增了大量的统计收集器来来获取流中的元素的一些统计信息。
     * 简单来说就是可以用一些聚合函数，如求和，取最大/小值，平均值等等*/
    private static void Statistics(List<Integer> numbers, List<Student> students) {
        /*经过一系列计算后，使用 summaryStatistics() 即可*/
        IntSummaryStatistics statistics = numbers.stream()
                .mapToInt(StreamApi::multip)
                .distinct()
                .filter(e -> e > 0)
                /*默认为升序排序，如果想为倒序：参数加上 Comparator.reverseOrder() */
                .sorted()
                .summaryStatistics();
        logger.info(new StringBuilder().append("summaryStatistics()->最大：").append(statistics.getMax())
                .append(",最小：").append(statistics.getMin())
                .append(",求和：").append(statistics.getSum())
                .append(",总量：").append(statistics.getCount())
                .append(",平均值：").append(statistics.getAverage())
                .toString());
        
        /*获取学生年龄的统计参数*/
        IntSummaryStatistics statistics2 = students.stream()
                .mapToInt(Student::getAge)
                .summaryStatistics();
        logger.info(new StringBuilder()
                .append("summaryStatistics()->最大：").append(statistics2.getMax())
                .append(",最小：").append(statistics2.getMin())
                .append(",求和：").append(statistics2.getSum())
                .append(",总量：").append(statistics2.getCount())
                .append(",平均值：").append(statistics2.getAverage())
                .toString());
    }
    
    /** sorted() 方法用于给流中的元素进行排序。 */
    private static void sorted(List<Integer> numbers, List<Student> students) {
        /*JDK7的写法*/
        List<Integer> demo5 = new ArrayList<>();
        List<Integer> listToSort = new ArrayList<>();
        Set<Integer> uniqueValues = new HashSet<>();
        for (Integer number : numbers) {
            Integer e = multip(number);
            if (uniqueValues.add(e)) {
                if (e > 0) {
                    listToSort.add(e);
                }
            }
        }
        listToSort.sort(Comparator.reverseOrder());
        long limit1 = 3;
        for (Integer e : listToSort) {
            if (limit1-- == 0) {
                break;
            }
            demo5.add(e);
        }
        
        demo5 = numbers.stream()
                .map(StreamApi::multip)
                .distinct()
                .filter(e -> e > 0)
                /*不加参数，默认为升序排序，如果想为倒序：参数加上 Comparator.reverseOrder() */
                .sorted(Comparator.reverseOrder())
                /* 对象排序的方法，排序规则根据对象里面的属性来排序 */
                /*.sorted(Comparator.comparing(Student::getAge))*/
                .limit(3).collect(Collectors.toList());
        logger.info(new StringBuilder().append("sorted()->").append(demo5).toString());
        
        /*筛选性别为女，且不重复的学生名称，只取前2，并排序,拼接姓名并且以逗号分隔*/
        String list = students.stream()
                .filter(student -> student.getSex().equals("女"))
                .map(Student::getName)
                .distinct()
                .limit(2)
                .sorted().collect(Collectors.joining(","));
        logger.info(new StringBuilder().append("sorted()->").append(list).toString());
    }
    
    /** limit() 方法用于减少( 限制 ) 流中的元素数量。 */
    private static void limit(List<Integer> numbers, List<Student> students) {
        /*JDK7的写法*/
        List<Integer> demo4 = new ArrayList<>();
        long limit = 2;
        Set<Integer> uniqueValues = new HashSet<>();
        for (Integer number : numbers) {
            Integer e = multip(number);
            if (uniqueValues.add(e)) {
                if (e > 4) {
                    if (limit-- == 0) {
                        break;
                    }
                    demo4.add(e);
                }
            }
        }
        
        demo4 = numbers.stream()
                .map(StreamApi::multip)
                .distinct()
                .filter(e -> e > 4)
                /*只取前2*/
                .limit(2).collect(Collectors.toList());
        logger.info(new StringBuilder().append("limit()->").append(demo4).toString());
        /*筛选性别为女，且不重复的学生名称，只取前2,拼接姓名并且以逗号分隔*/
        String list = students.stream()
                .filter(student -> student.getSex().equals("女"))
                .map(Student::getName)
                .distinct()
                .limit(2).collect(Collectors.joining(","));
        logger.info(new StringBuilder().append("limit()->").append(list).toString());
    }
    
    /** filter() 方法根据一个谓词来过滤元素。这个谓词是一个方法，以流中的每一个元素作为参数，如果返回 false 则会被过滤掉。 */
    private static void filter(List<Integer> numbers, List<Student> students) {
        /*JDK7的写法*/
        List<Integer> demo3 = new ArrayList<>();
        Set<Integer> uniqueValues = new HashSet<>();
        for (Integer number : numbers) {
            Integer e = multip(number);
            if (uniqueValues.add(e)) {
                if (e > 4) {
                    demo3.add(e);
                }
            }
        }
        
        demo3 = numbers.stream()
                .map(StreamApi::multip)
                .distinct()
                /*满足条件的被留下，也就是为true的被留下*/
                .filter(e -> e > 4).collect(Collectors.toList());
        logger.info(new StringBuilder().append("filter()->").append(demo3).toString());
        
        /*筛选性别为男，且不重复的学生名称,拼接姓名并且以逗号分隔*/
        String list = students.stream()
                .filter(student -> student.getSex().equals("男"))
                .map(Student::getName)
                .distinct().collect(Collectors.joining(","));
        logger.info(new StringBuilder().append("filter()->").append(list).toString());
    }
    
    /**
     * distinct() 方法会返回由该流的不同元素（根据Object.equals(Object) ）组成的流。
     * 对于有序流，选择不同的元素是稳定的（对于重复的元素，首先在遇到顺序中出现的元素被保留。）对于无序流，不能保证稳定性。
     * 保存稳定性为distinct()在并行管线是相对昂贵的（要求操作充当一个完整屏障，具有大量缓冲的开销）通常不需要的，和稳定性。
     * 使用无序流源（如generate(Supplier) ）或具有除去排序约束BaseStream.unordered()可导致显著更高效的执行为distinct()在并行管线，
     * 如果情况许可的语义。如果需要与遇到顺序一致， distinct()在并行流水线中使用distinct()您的性能或内存利用率不佳，
     * 则使用BaseStream.sequential()切换到顺序执行可能会提高性能。
     */
    private static void distinct(List<Integer> numbers, List<Student> students) {
        /*JDK7的写法*/
        List<Integer> demo2 = new ArrayList<>();
        Set<Integer> uniqueValues = new HashSet<>();
        for (Integer number : numbers) {
            Integer multip = multip(number);
            if (uniqueValues.add(multip)) {
                demo2.add(multip);
            }
        }
        
        /*使用 map() 方法把求出每个元素的平方，然后过滤掉重复的元素，最后再转换为列表集合*/
        demo2 = numbers.stream()
                .map(StreamApi::multip)
                .distinct().collect(Collectors.toList());
        logger.info(new StringBuilder().append("distinct()->").append(demo2).toString());
        
        /*去掉重复的学生,拼接姓名并且以逗号分隔*/
        String list = students.stream()
                .distinct()
                .map(Student::getName).collect(Collectors.joining(","));
        logger.info(new StringBuilder().append("distinct()->").append(list).toString());
    }
    
    /** map() 方法会迭代流中的元素，并为每个元素应用一个方法，然后返回应用后的流。 */
    private static void map(List<Integer> numbers, List<Student> students) {
        /*JDK7的写法*/
        List<Integer> demo1 = new ArrayList<>();
        for (Integer i : numbers) {
            Integer multip = multip(i);
            demo1.add(multip);
        }
        
        /*使用 map() 方法,把求出每个元素的平方，再转换为列表集合*/
        demo1 = numbers.stream().map(i -> multip(i)).collect(Collectors.toList());
        logger.info(new StringBuilder().append("map()->").append(demo1).toString());
        
        /*将所有学生的名称以集合的形式打印出来,拼接姓名并且以逗号分隔*/
        String list = students.stream().map(Student::getName).collect(Collectors.joining(","));
        logger.info(new StringBuilder().append("map()->").append(list).toString());
    
        /*除了Stream ，其为对象引用的流，存在原语特为IntStream ， LongStream和DoubleStream ，
        所有这些都称为“流”和符合此处描述的特征和限制。*/
        
        /*其他流：map还有自动转化功能，可以将Stream流转化为 IntStream、LongStream、DoubleStream 等流。
        mapToInt() 返回一个 IntStream ，其中包含将给定函数应用于此流的元素的结果。
        mapToLong() 返回一个 LongStream ，其中包含将给定函数应用于此流的元素的结果。
        mapToDouble() 返回一个 DoubleStream ，其中包含将给定函数应用于此流的元素的结果。
         * 如：mapToInt()获取学生年龄的统计参数*/
        IntSummaryStatistics statistics2 = students.stream()
                .mapToInt(Student::getAge)/*返回数值流，减少拆箱封箱操作，避免占用内存  IntStream*/
                .summaryStatistics();
        logger.info(new StringBuilder()
                .append("summaryStatistics()->最大：").append(statistics2.getMax())
                .append(",最小：").append(statistics2.getMin())
                .append(",求和：").append(statistics2.getSum())
                .append(",总量：").append(statistics2.getCount())
                .append(",平均值：").append(statistics2.getAverage())
                .toString());
    
        /*stream中的 flatmap 是stream的一种中间操作，它和stream的map一样，是一种收集类型的stream中间操作，
        但是与map不同的是，它可以对stream流中单个元素再进行拆分（切片），从另一种角度上说，使用了它，就是使用了双重for循环。
        简单说 同样的双重for循环方法：
              map     是返回  stream<stream<T>> 类型的流
              flatmap 是返回 stream<T> 的流 */
        
        // 找到所有数学老师的学生的家长的电话,并找他们开家长会
        /*List<Parents> collect = teacs.stream()
                // 过滤数学老师
                .filter(t -> Subject.Math.getValue().equals(t.getSubject()))
                // 通过老师找学生
                .flatMap(t -> stus.stream().filter(s -> s.getTechId().equals(t.getId())))
                // 过滤重复的学生(使用student的equals和hashCode方法)
                .distinct()
                // 通过学生找家长(这里就简化为创建家长对象)
                .map(s -> {
                    Parents p = new Parents();
                    p.setId(UUID.randomUUID().toString());
                    p.setChirldId(s.getId());
                    p.setName(s.getName().toUpperCase() + "'s Parent");
                    p.setEmail((int) (Math.random() * 1000000) + "@qq.com");
                    return p;
                })
                .collect(Collectors.toList());*/
        /*参考来源：https://www.cnblogs.com/xfyy-2020/p/13289066.html*/
        
        /*其他流：flatMap还有自动转化功能，可以将Stream流转化为 IntStream、LongStream、DoubleStream 等流。
        flatMapToInt() 返回一个IntStream ，其中包含将该流的每个元素替换为通过将提供的映射函数应用于每个元素而产生的映射流的内容的结果。
        每个映射的流在其内容被放入此流之后是closed 。（如果映射的流为null则使用空的流。）
        flatMapToLong() 返回一个 LongStream 如上...
        flatMapToDouble() 返回一个 DoubleStream 如上...
         * 如：mapToInt()获取学生年龄的统计参数*/
    }
    
    /**
     * 循环的方法合集
     *
     * @param numbers List->[3, 2, 2, 3, 7, 3, 5]
     */
    private static void forEach(List<Integer> numbers, List<Student> students) {
        /*forEach 对此流的每个元素执行操作。
         这个操作的行为显然是不确定的。 对于并行流管道，此操作不保证遵守流的遇到顺序，因为这样做将牺牲并行性的好处。
         对于任何给定的元素，动作可以在图书馆选择的任何时间和任何线索中执行。 如果操作访问共享状态，则负责提供所需的同步。
         注意：如果是并行流，并且需要保证顺序，推荐用 forEachOrdered */
        numbers.stream().forEach(e -> System.out.println(e));
        numbers.forEach(System.out::println);
        /*并行流*/
        numbers.parallelStream().forEach(System.out::println);
        students.parallelStream().forEach(e -> System.out.println(e.getName()));
    
        /*forEachOrdered 如果流具有定义的遇到顺序，则以流的遇到顺序对该流的每个元素执行操作。
        此操作一次处理元素，如果存在，则按照遇到的顺序进行处理。 执行一个元素happens-before执行后续元素的操作的操作，
        但对于任何给定的元素，该操作可以在库选择的任何线程中执行。*/
        numbers.stream().forEachOrdered(System.out::println);
        /*并行流*/
        numbers.parallelStream().forEachOrdered(System.out::println);
        students.parallelStream().forEachOrdered(e -> System.out.println(e.getName()));
    }
    
    /**
     * 乘法
     *
     * @param i Integer
     *
     * @return i * i
     */
    private static int multip(Integer i) {
        return i * i;
    }
}